package com.rhino.config;

import com.rhino.security.constants.SecurityConstants;
import com.rhino.security.datasourceaccess.MyAccessDecisionManager;
import com.rhino.security.datasourceaccess.MyFilterInvocationSecurityMetadataSource;
import com.rhino.security.filter.JwtAuthenticationTokenFilter;
import com.rhino.security.handler.*;
import com.rhino.security.service.MyPasswordEncoder;
import com.rhino.security.service.impl.JwtUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.List;


@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;

    @Autowired
    private RestAccessDeniedHandler restAccessDeniedHandler;

    @Autowired
    private JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;

    @Autowired
    private JwtAuthenticationFailureHandler jwtAuthenticationFailureHandler;

    @Autowired
    private JwtLogoutSuccessHandler jwtLogoutSuccessHandler;


    /**
     * 从容器中取出 AuthenticationManagerBuilder，执行方法里面的逻辑之后，放回容器
     */
    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(jwtUserDetailsService()).passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 设置不需要授权访问的请求地址
        List<String> authAntmatchers = SecurityConstants.AUTH_ANTMATCHERS;

        http.csrf().disable()

                //设置请求的资源配置
                .authorizeRequests()
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                .antMatchers(authAntmatchers.toArray(new String[authAntmatchers.size()])).permitAll()

                //可配置访问指定的URL，需要哪些角色；
//                .antMatchers("/Admin/**").hasRole("admin")
                //动态资源访问控制；访问当前URL当前用户是否有权限？；菜单权限控制一般系统数据库维护；
                //正常业务用户登录后，通过当前用户及角色获取用户可以访问的菜单给前端；但是用户也可以拼接URL访问，此时后台需要URL资源访问进行控制。
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(accessDecisionManager());
                        object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource());
                        return object;
                    }
                })
                .anyRequest().authenticated()

                //登录信息配置 登录页面或接口地址，
                .and()
                .formLogin().loginProcessingUrl(SecurityConstants.LOGIN_PROCESSING_URL)
                .successHandler(jwtAuthenticationSuccessHandler)
                .failureHandler(jwtAuthenticationFailureHandler)
                .and()
                .logout().logoutUrl(SecurityConstants.LOGOUT_URL)
                .logoutSuccessHandler(jwtLogoutSuccessHandler)

                //Session控制
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .headers().cacheControl();

        // 处理异常情况：认证失败和权限不足
        http.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler)
                .accessDeniedHandler(restAccessDeniedHandler);

        /**
         * 添加自定义过滤器，校验Token，需要在用户校验之前校验。
         * 在 UsernamePasswordAuthenticationFilter 之前添加 JwtAuthenticationTokenFilter
         */
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
        CorsConfiguration cors = new CorsConfiguration();
        cors.setAllowCredentials(true);
        cors.addAllowedOrigin("*");
        cors.addAllowedHeader("*");
        cors.addAllowedMethod("*");
        configurationSource.registerCorsConfiguration("/**", cors);
        return new CorsFilter(configurationSource);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new MyPasswordEncoder();
    }

    //获取UserDetailsService
    @Bean
    public JwtUserDetailsServiceImpl jwtUserDetailsService() {
        return new JwtUserDetailsServiceImpl();
    }
    //判断访问当前URL，当前用户是否有对应的权限
    @Bean
    public AccessDecisionManager accessDecisionManager(){
        return new MyAccessDecisionManager();
    }

    //获取访问当前URL，需要哪些角色
    @Bean
    public FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource(){
        return new MyFilterInvocationSecurityMetadataSource();
    }


}